// Copyright (c) Appeon. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;

namespace SnapObjects.Data
{
    internal class EntryTracker<TModel> : IModelTracker
    {
        private readonly IMapperContext _mapperContext;
        private readonly ModelTracker<TModel> _modelTracker;
        private readonly IStatefulModel _statefulModel;

        public EntryTracker(
            IMapperContext mapperContext,
            IModelEntry<TModel> modelEntry,
            bool changedOnly,
            Action<ISaveContext> beforeSaveAction,
            Action<ISaveContext> afterSaveAction)
        {
            _mapperContext = mapperContext;

            this.CompositeModelAccessor = (ICompositeModelAccessor<TModel>)_mapperContext
                                                .GetCompositeModelAccessor(typeof(TModel));
            _statefulModel = EntryStatefulModelFactory.Create(this.CompositeModelAccessor, modelEntry, ChangeTrackingStrategy.Snapshot);

            if (!changedOnly ||
                modelEntry.ModelState == ModelState.NewModified ||
                modelEntry.ModelState == ModelState.Modified ||
                modelEntry.ModelState == ModelState.Deleted)
            {
                if (modelEntry.ModelState == ModelState.NewModified ||
                    modelEntry.ModelState == ModelState.New)
                {
                    _modelTracker = new CreateTracker<TModel>(mapperContext, _statefulModel,
                                                                beforeSaveAction, afterSaveAction);
                }
                else if (modelEntry.ModelState == ModelState.Modified ||
                    modelEntry.ModelState == ModelState.NotModified)
                {
                    _modelTracker = new UpdateTracker<TModel>(mapperContext, _statefulModel,
                                                                beforeSaveAction, afterSaveAction, false);
                }
                else if (modelEntry.ModelState == ModelState.Deleted)
                {
                    _modelTracker = new DeleteTracker<TModel>(mapperContext, _statefulModel,
                                                                beforeSaveAction, afterSaveAction);
                }
            }
            else if (modelEntry.ModelState == ModelState.NotModified)
            {
                //To support master/detail track SetValueAttribute when master is not changed
                _modelTracker = new UpdateTracker<TModel>(mapperContext, _statefulModel,
                                                            beforeSaveAction, afterSaveAction, true);
            }
        }

        /// <summary>
        ///     Occurs when the current value is changed.
        /// </summary>
        public event Action<string, object> CurrentValueChanged
        {
            add => _modelTracker.CurrentValueChanged += value;
            remove => _modelTracker.CurrentValueChanged -= value;
        }

        public TrackerType TrackerType => TrackerType.ModelEntry;

        public TModel Current => (TModel)_statefulModel.Current;

        public PocoModelMeta ModelMeta => (PocoModelMeta)this.CompositeModelAccessor.ModelMeta;

        /// <summary>
        ///     Gets the tracked state of the data.
        /// </summary>
        public ModelState ModelState => _statefulModel.LatestModelState;

        public ICompositeModelAccessor<TModel> CompositeModelAccessor { get; }

        /// <summary>
        ///     Gets the property names of the model class.
        /// </summary>
        public string[] Properties => this.ModelMeta.Properties.Select(o => o.Name).ToArray();

        /// <summary>
        ///     Gets the tracked state of the specified property.
        /// </summary>
        /// <param name="name">The name of the property.</param>
        /// <returns>The tracked state of the specified property.</returns>
        public PropertyState GetPropertyState(string name)
        {
            return _statefulModel.GetPropertyState(this.ModelMeta.IndexOf(name));
        }

        /// <summary>
        ///     Gets the original value of the specified property.
        /// </summary>
        /// <param name="name">The name of the property.</param>
        /// <returns>The original value of the specified property.</returns>
        public object GetOriginalValue(string name)
        {
            return _statefulModel.GetOriginalValue(name);
        }

        /// <summary>
        ///     Gets the current value of the specified property.
        /// </summary>
        /// <param name="name">The name of the property.</param>
        /// <returns>The current value of the specified property.</returns>
        public object GetCurrentValue(string name)
        {
            return _statefulModel.GetCurrentValue(name);
        }

        public void SetEmbeddedValue(string name, object value)
        {
            this.CompositeModelAccessor.SetValue(this.Current, name, value);
        }

        public IReadOnlyList<IUpdatableModel> GetUpdateableModels()
        {
            return _modelTracker == null ?
                        new List<IUpdatableModel>() : _modelTracker.GetUpdateableModels();
        }

        public IUpdatableModel GetMasterModel()
        {
            return _modelTracker?.GetMasterModel();
        }

        public virtual IList<ValidationResult> Validate()
        {
            return _modelTracker.Validate();
        }

        /// <summary>
        ///     Sets the current value for the specified property.
        /// </summary>
        /// <param name="name">The name of the property.</param>
        /// <param name="value">The value set to the specified property.</param>
        public void SetCurrentValue(string name, object value)
        {
            _modelTracker.SetCurrentValue(name, value);
        }
    }
}
